// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/dydx_futures/api_util/rate_tracker.h"

#include <algorithm>
#include <sstream>
#include <string>

#include <boost/algorithm/string/replace.hpp>
#include <glog/logging.h>

#include "coin2/base/proto_util.h"
#include "coin2/exchange/base/order/rate_config.h"

namespace coin2::exchange::dydx_v3::api_util {

constexpr auto EXAMPLE_FUTURES_DYDX = R"(
"connection_config": {
  "key_filepath": "/home/ec2-user/config/trade/xunke18/dydx_futures/trade_key.json",
  "rate_limit_info": {
    "futures_dydx": {
      "req": {
        "secs": 10,
        "reqs": 100
      },
      "order": {
        "secs": 10,
        "reqs": 1000
      },
      "cancel": {
        "secs": 10,
        "reqs": 7000
      }
    }
  }
},)";

template <class RateTracker>
void CheckFail(RateTracker&& rate_tracker, bool check_result) {
  if (!check_result) {
    LOG(INFO) << "Hit rate limit on [" << rate_tracker->GetName() << "]";
  }
}

DydxRateTracker::DydxRateTracker(
    const MarketExchangeApi& mea,
    const coin2::exchange::order::OrderConnectionConfig& cfg) {
  using RateConfig = coin2::exchange::base::order::RateConfig;

  RateConfig::Get()->GetConfig(mea, cfg, &rate_config_);
  LOG(INFO) << "dydx rate tracing config example: " << EXAMPLE_FUTURES_DYDX;
  LOG(INFO) << "dydx rate tracking config: " << rate_config_.DebugString();

  int64_t win_size = 0;
  int max_points = 0;

  win_size = rate_config_.req().secs() * 1'000'000'000LL;
  max_points = rate_config_.req().reqs();
  req_rate_tracker_ = std::make_unique<FixedWindowRateTracker>(win_size);
  // 100 points per 10 seconds
  req_rate_tracker_->SetRateLimitInfo(max_points, max_points);
  req_rate_tracker_->SetName("dydx query (10 seconds)");

  // rate limit report
  rate_limit_report_writer_ = std::make_unique<RateLimitReportWriter>();
  rate_limit_report_writer_->SetRateLimitInfoString(&rate_config_);
}

void DydxRateTracker::SetNativeSymbol(const std::string& native_symbol) {
  native_symbol_ = native_symbol;
}

auto DydxRateTracker::GetOrderRateTracker(const std::string& native_symbol) -> RateTrackerType& {
  native_symbol_ = native_symbol;
  if (order_rate_tracker_.count(native_symbol) == 0) {
    int64_t win_size = rate_config_.order().secs() * 1'000'000'000LL;
    int max_points = rate_config_.order().reqs();
    order_rate_tracker_[native_symbol] = std::make_unique<FixedWindowRateTracker>(win_size);
    // according to doc, max points is 100, but test shows it is 7000.
    order_rate_tracker_[native_symbol]->SetRateLimitInfo(max_points, max_points);
    order_rate_tracker_[native_symbol]->SetName("dydx cancel (10 seconds) " + native_symbol);
  }
  return order_rate_tracker_[native_symbol];
}

auto DydxRateTracker::GetCancelRateTracker(const std::string& native_symbol) -> RateTrackerType& {
  native_symbol_ = native_symbol;
  if (cancel_rate_tracker_.count(native_symbol) == 0) {
    int64_t win_size = rate_config_.cancel().secs() * 1'000'000'000LL;
    int max_points = rate_config_.cancel().reqs();
    cancel_rate_tracker_[native_symbol] = std::make_unique<FixedWindowRateTracker>(win_size);
    // according to doc, max points is 100, but test shows it is 7000.
    cancel_rate_tracker_[native_symbol]->SetRateLimitInfo(max_points, max_points);
    cancel_rate_tracker_[native_symbol]->SetName("dydx cancel (10 seconds) " + native_symbol);
  }
  return cancel_rate_tracker_[native_symbol];
}

bool DydxRateTracker::HasEnoughRateLimit(int64_t timestamp, ActionType action_type) {
  bool ret = true;
  if (action_type == ActionType::PLACE) {
    const auto& order_rate_tracker = GetOrderRateTracker(native_symbol_);
    ret = order_rate_tracker->GetRemaining(timestamp) > 0;
    CheckFail(order_rate_tracker, ret);
  } else if (action_type == ActionType::CANCEL) {
    const auto& cancel_rate_tracker = GetCancelRateTracker(native_symbol_);
    ret = cancel_rate_tracker->GetRemaining(timestamp) > 0;
    CheckFail(cancel_rate_tracker, ret);
  } else {
    ret = req_rate_tracker_->GetRemaining(timestamp) > 0;
    CheckFail(req_rate_tracker_, ret);
  }

  if (!ret) {
    rate_limit_report_writer_->SetHitRateLimit(timestamp, action_type);
  }
  return ret;
}

void DydxRateTracker::Push(int32_t num_reqs, int64_t timestamp, ActionType action_type) {
  if (action_type == ActionType::PLACE) {
    CHECK_GT(native_symbol_.size(), 0);
    auto& order_rate_tracker = GetOrderRateTracker(native_symbol_);
    order_rate_tracker->Push(num_reqs, timestamp);
  } else if (action_type == ActionType::CANCEL) {
    CHECK_GT(native_symbol_.size(), 0);
    CHECK_EQ(num_reqs, 1);
    auto& cancel_rate_tracker = GetCancelRateTracker(native_symbol_);
    cancel_rate_tracker->Push(num_reqs, timestamp);
  } else {
    CHECK_EQ(num_reqs, 1);
    req_rate_tracker_->Push(num_reqs, timestamp);
  }
}

void DydxRateTracker::UpdateRateLimitInfo(const std::string& limit_info_str) {
  auto limit_info = nlohmann::json::parse(limit_info_str);

  std::string method = limit_info["method"].get<std::string>();
  int64_t offset = (limit_info["ratelimit-reset"].get<int64_t>() % 10000LL) * 1'000'000LL;
  int total = limit_info["ratelimit-limit"].get<int64_t>();
  int remain = limit_info["ratelimit-remaining"].get<int64_t>();

  auto ts = GetCurrentTimestamp();
  if (method == "GET") {
    DLOG(INFO) << req_rate_tracker_->MaxReq() << ", " << req_rate_tracker_->GetUsedRate(ts) << ", "
              << offset;
    DLOG(INFO) << limit_info_str;
    req_rate_tracker_->SetRateLimitInfo(total, remain);
    req_rate_tracker_->set_offset(offset);
  } else if (method == "POST") {
    std::string native_symbol = limit_info["native_symbol"].get<std::string>();
    auto& order_rate_tracker = GetOrderRateTracker(native_symbol);

    DLOG(INFO) << order_rate_tracker->MaxReq() << ", " << order_rate_tracker->GetUsedRate(ts) << ", "
              << offset << ", " << native_symbol;
    DLOG(INFO) << limit_info_str;

    order_rate_tracker->SetRateLimitInfo(total, remain);
    order_rate_tracker->set_offset(offset);
  } else if (method == "DELETE") {
    std::string native_symbol = limit_info["native_symbol"].get<std::string>();
    auto& cancel_rate_tracker = GetCancelRateTracker(native_symbol);

    DLOG(INFO) << cancel_rate_tracker->MaxReq() << ", " << cancel_rate_tracker->GetUsedRate(ts)
              << ", " << offset << ", " << native_symbol;
    DLOG(INFO) << limit_info_str;

    cancel_rate_tracker->SetRateLimitInfo(total, remain);
    cancel_rate_tracker->set_offset(offset);
  } else {
    CHECK(false) << "unknown method!";
  }
}

void DydxRateTracker::SetRateLimitInfoString(const google::protobuf::Message* message) {
  if (!rate_limit_report_writer_) {
    return;
  }
  rate_limit_report_writer_->SetRateLimitInfoString(message);
}

void DydxRateTracker::WriteRateLimitReportMessage(OrderLoggerCB callback) {
  if (!callback) {
    return;
  }
  rate_limit_report_writer_->Write(callback);
}

double DydxRateTracker::GetMaxRateUsedRatio(int64_t timestamp, ActionType action_type) {
  switch (action_type) {
    case ActionType::PLACE: {
      // auto max_reqs = order_rate_tracker_->MaxReq();
      // auto remaining = order_rate_tracker_->GetRemaining(timestamp);
      // return 1.0 * (max_reqs - remaining) / max_reqs;
      return 0;
    }
    case ActionType::CANCEL: {
      // auto max_reqs = cancel_rate_tracker_->MaxReq();
      // auto remaining = cancel_rate_tracker_->GetRemaining(timestamp);
      // return 1.0 * (max_reqs - remaining) / max_reqs;
      return 0;
    }
    case ActionType::QUERY: {
      auto max_reqs = req_rate_tracker_->MaxReq();
      auto remaining = req_rate_tracker_->GetRemaining(timestamp);
      return 1.0 * (max_reqs - remaining) / max_reqs;
    }
    default: {
      // shouldn't reached here
      LOG(ERROR) << "ops~ something is wrong, action_type=" << action_type;
      return 0.;
    }
  }
}

}  // namespace coin2::exchange::dydx_v3::api_util
